﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using PgBirdie.Core.Config;
using PgBirdie.Core.Extensions;
using PgBirdie.Core.Helpers;
using PgBirdie.Core.Model;

namespace PgBirdie.Core.Service.Dump
{
    public class PgDumpService : IPgDumpService
    {
        private readonly Process _process;

        public PgDumpService()
        {
            _process = new Process();
            _process.StartInfo.RedirectStandardError = true;
            _process.StartInfo.RedirectStandardOutput = true;
            _process.StartInfo.UseShellExecute = false;

            // Set console code page to UTF-8 (i.e. 65001).
            // Otherwise, UTF-8 characters may become corrupted, e.g.
            // with default code page Å and ô become ├à and ├┤, respectively.
            _process.StartInfo.FileName = @"C:\Windows\System32\chcp.com";
            _process.StartInfo.Arguments = " 65001";
            _process.Start();
            var error = _process.StandardError.ReadLine();
            _process.WaitForExit();
            if (!String.IsNullOrEmpty(error))
                throw new Exception("Could not set console code page to UTF-8 (65001). Character corruption could occur.");

            _process.StartInfo.FileName = Settings.PathTo.PgDump;
        }

        public string GetRawStructureFromPgDump(DatabaseType type)
        {
            PopulateEnvironmentVariablesForProcess(type);

            _process.StartInfo.Arguments = String.Format(
                @" --encoding=utf8 --schema-only --create {0}",
                type == DatabaseType.Target ? Settings.Target.Database : Settings.Source.Database);

            _process.Start();

            var output = _process.StandardOutput.ReadToEnd();
            _process.WaitForExit();

            var error = _process.StandardError.ReadLine();
            _process.WaitForExit();

            if (!String.IsNullOrEmpty(error))
            {
                AppConsole.WriteLine("{0}", error);
                return null;
            }

            if (!String.IsNullOrEmpty(output) && !ValidatePgDump(output))
            {
                AppConsole.WriteLine("Pg_dump.exe structure dump was invalid. Please validate your pg_dump.exe settings and/or redump.");
                Environment.Exit(-1);
            }

            return output;
        }

        public string GetRawStaticDataFromPgDump(DatabaseType type)
        {
            PopulateEnvironmentVariablesForProcess(type);

            var tables = new List<string>();
            foreach (var schema in Settings.Source.Schemas)
                foreach (var table in schema.Value.Tables)
                    if (table.Value.WithData)
                        tables.Add(String.Format(@"\""{0}\"".\""{1}\""", schema.Value.Name, table.Value.Name));

            if (tables.IsNullOrEmpty())
                return String.Empty;

            _process.StartInfo.Arguments = String.Format(
                @" --encoding=utf8 --data-only --inserts -t {0} {1}",
                String.Join(" -t ", tables.ToArray()),
                type == DatabaseType.Target ? Settings.Target.Database : Settings.Source.Database);

            _process.Start();

            var output = _process.StandardOutput.ReadToEnd();
            _process.WaitForExit();

            var error = _process.StandardError.ReadLine();
            _process.WaitForExit();

            if (!String.IsNullOrEmpty(error))
            {
                AppConsole.WriteLine("{0}", error);
                return null;
            }

            if (!String.IsNullOrEmpty(output) && !ValidatePgDump(output))
                throw new Exception(
                    "Pg_dump.exe static data dump was invalid. Please validate your pg_dump.exe settings and/or redump.");

            return output;
        }        

        private void PopulateEnvironmentVariablesForProcess(DatabaseType type)
        {
            // To make sure no collisions take place.
            CleanupEnvironmentVariables();

            _process.StartInfo.EnvironmentVariables.Add("PGHOSTADDR",
                                                        type == DatabaseType.Target
                                                            ? Settings.Target.Host
                                                            : Settings.Source.Host);
            _process.StartInfo.EnvironmentVariables.Add("PGPORT",
                                                        type == DatabaseType.Target
                                                            ? Settings.Target.Port
                                                            : Settings.Source.Port);
            _process.StartInfo.EnvironmentVariables.Add("PGUSER",
                                                        type == DatabaseType.Target
                                                            ? Settings.Target.Role
                                                            : Settings.Source.Role);
            _process.StartInfo.EnvironmentVariables.Add("PGPASSWORD",
                                                        type == DatabaseType.Target
                                                            ? Settings.Target.Password
                                                            : Settings.Source.Password);
            _process.StartInfo.EnvironmentVariables.Add("PGDATABASE",
                                                        type == DatabaseType.Target
                                                            ? Settings.Target.Database
                                                            : Settings.Source.Database);
        }

        private void CleanupEnvironmentVariables()
        {
            _process.StartInfo.EnvironmentVariables.Remove("PGHOSTADDR");
            _process.StartInfo.EnvironmentVariables.Remove("PGPORT");
            _process.StartInfo.EnvironmentVariables.Remove("PGUSER");
            _process.StartInfo.EnvironmentVariables.Remove("PGPASSWORD");
            _process.StartInfo.EnvironmentVariables.Remove("PGDATABASE");
            _process.StartInfo.EnvironmentVariables.Remove("PGOPTIONS");
        }
        
        /// <summary>
        /// Determines if dump provided by pg_dump.exe is complete/valid using heuristics.
        /// </summary>
        private bool ValidatePgDump(string text)
        {
            var totalLines = text.Split(Settings.SplitterFor.WindowsLineBreak, StringSplitOptions.None);

            var dumpCompleteLine = totalLines[totalLines.Length - 4].Split(Settings.SplitterFor.Space,
                                                                                 StringSplitOptions.RemoveEmptyEntries);

            return dumpCompleteLine.Length == 5 && dumpCompleteLine[0].Equals("--") && dumpCompleteLine[4].Equals("complete");
        }
    }
}